/*
The MIT License (MIT)

Copyright (c) 2013 Mike Dapiran, Brian May, Richard Pospesel, and Bert Wierenga

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software 
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include "ContentLibrary.h"
#include "tinyxml.h"
#include "../Content/Scene.h"
#include "../Content/Prefab.h"

#include <crtdbg.h>

namespace Hogshead
{
	namespace Core
	{
		HashTable<String, IContent*>* ContentLibrary::_prototype_library = NULL;
		HashTable<String, IContent*>* ContentLibrary::_content_table = NULL;
		int ContentLibrary::_num_types = 0;

		bool ContentLibrary::_given_env_var = false;
		String ContentLibrary::_content_path;

		Logger* ContentLibrary::_logger = NULL;

		void ContentLibrary::write(const char* in_message)
		{
			ContentLibrary::_logger->write(in_message);
		}

		bool ContentLibrary::prefabIsLoaded(const String& in_key)
		{
			IContent* requested_prefab = NULL;
			_content_table[Prefab::contentID()].tryGetValue(in_key, requested_prefab);
			if( requested_prefab )
				return true;

			return false;
		}

		void ContentLibrary::setContentPath(const String& in_path)
		{
			_content_path = in_path;
			_given_env_var = true;
		}

		void ContentLibrary::setContentEnvironmentVariable( const String& in_variable_name )
		{
			if(_given_env_var)
				return;

			char buffer[1024];
			memset(buffer, 0xDF, 1024);

			GetEnvironmentVariableA( (LPCSTR)in_variable_name, buffer, 1024 );

			//save off as a string
			_content_path = buffer;
			_given_env_var = true;
		}

		String ContentLibrary::getCompleteContentString( const String& in_local_path )
		{
			if( !_given_env_var )
			{
				return in_local_path;
			}

			String result = StringBuilder() << ContentLibrary::_content_path << "/" << in_local_path;
			return result;
		}

		String ContentLibrary::getRelativePath(const String& in_abso_path)
		{
			if( !_given_env_var )
			{
				return in_abso_path;
			}

			return String(in_abso_path, ContentLibrary::_content_path.length(), in_abso_path.length() - ContentLibrary::_content_path.length());
		}

		void ContentLibrary::initialize()
		{
			
			_logger = new Logger("logs/content_library.log");

			IEnumerator<Pair<String, IContent*>>& iter = _prototype_library->getEnumerator();

			int count = 0;
			while(iter.moveNext())
			{
				iter.current().second->setContentID( count++ );
			}
			
			_content_table = new HashTable<String, IContent*>[_prototype_library->elements()];
			_num_types = _prototype_library->elements();
		}

	

		void ContentLibrary::printOut()
		{
			HashTable<String, IContent*>* content_table;

			for( int type_index = 0; type_index < _num_types; type_index++ )
			{

				//iterate through each hash table
				content_table = &_content_table[type_index];
				IEnumerator<Pair<String, IContent*>>& iter = content_table->getEnumerator();

				
				while(iter.moveNext())
				{
					hout << "Holding content  key: " << iter.current().second->_key << " path: " << iter.current().second->_path << " count: " << iter.current().second->_ref_count << endl << endl;
				}

			}

		}

		void ContentLibrary::purge()
		{
			int release_count;
			Vector<Pair<String, IContent*>> to_be_removed;
			do
			{
				release_count = 0;
				HashTable<String, IContent*>* content_table;
			
				//iterate through each hash table, releasing
				for( int type_index = 0; type_index < _num_types; type_index++ )
				{					
					content_table = &_content_table[type_index];
					IEnumerator<Pair<String, IContent*>>& iter = content_table->getEnumerator();

					
					while(iter.moveNext())
					{
						//if something is still holding it, ignore it
						if(iter.current().second->_ref_count > 0)
							continue;

						//otherwise add it to the list of things to remove
						to_be_removed.add( iter.current() );				
					}	

					//now iterate through the list of key,Content pairs 
					//to be removed and do it
					for(int i = 0; i < to_be_removed.size(); i++)
					{
						content_table->remove(to_be_removed[i].first);
						Factory::release( to_be_removed[i].second );
						//keep track of releasing something
						release_count++;
					}
					to_be_removed.clear();
				}
			//if we released anything, repeat; it may have been holding onto 
			//another piece of content
			}while( release_count > 0 );


			
		}

		void ContentLibrary::finalize()
		{
			//iterate through all of the scenes and set ref counter to 0
			//the game is ending and should release all scenes
			HashTable<String, IContent*>& scene_table = _content_table[Scene::contentID()];
			IEnumerator<Pair<String, IContent*>>& en1 = scene_table.getEnumerator();
			while(en1.moveNext())
			{
				en1.current().second->_ref_count = 0;				
			}

			//then purge the content library
			ContentLibrary::purge();

#ifdef _DEBUG
			ContentLibrary::printOut();
			_ASSERTE( _CrtCheckMemory( ) );
#endif
			
			delete[] _content_table;

			// clear content prototypes from storage
			IEnumerator<Pair<String, IContent*>>& en2 = _prototype_library->getEnumerator();
			while(en2.moveNext())
			{
				Pair<String, IContent*>& p = en2.current();
				Factory::release(p.second);
			}
			delete _prototype_library;
			delete _logger;
		}

		void ContentLibrary::release(IContent* in_content)
		{
			in_content->_ref_count--;
			if( in_content->_ref_count< 0 )
				error("too much release");

			//if its a Scene then we also need to do a purge because there is only one
			//game and it is the only thing that would release a Scene (if all is well ).
			if(Scene* scene = in_content->as<Scene>() )
			{
				//remove it from the hash table
				_content_table[Scene::contentID()].remove( scene->getKey() );
				
				//release it to the factory
				Factory::release( scene );
			}
		}

		/**
		* To be used by IGame solely to change scenes without increasing ref count
		*/
		Scene* ContentLibrary::getScene(const String& in_key)
		{
			IContent* requested_scene = NULL;
			_content_table[Scene::contentID()].tryGetValue(in_key, requested_scene);
			return (Scene*)requested_scene;
		}


		void ContentLibrary::loadAndParseAssetList(const String& in_path)
		{
			FileInputStream fis(ContentLibrary::getCompleteContentString(in_path));

			StringBuilder sb;

			for(int i = fis.read(); i >= 0; i = fis.read())
				sb << (char)i;
			fis.close();

			TiXmlDocument document;
			document.Parse((String)sb);

			TiXmlElement* root_element = document.RootElement();
			String root_tag(root_element->Value());

			if(root_tag.equals("Root"))
			{
				TiXmlNode* child_node = NULL;
				TiXmlElement* child_element;
				while(child_node = root_element->IterateChildren(child_node))
				{
					child_element = child_node->ToElement();
					if(!child_element)
						continue;
					
					String tag(child_element->Value());

					if(tag.equals("AssetList"))
					{
						String new_xml(child_element->Attribute("path"));
						ContentLibrary::loadAndParseAssetList(new_xml);
					}
					else if(tag.equals("ContentXML"))
					{
						String new_xml(child_element->Attribute("path"));
						IContent* new_content = ContentLibrary::loadAndParseContentXML(new_xml);
						//decrement ref count back down since no class is actually referencing it. 
						new_content->_ref_count--;
					}
					else if(tag.equals("Content"))
					{
						String type = child_element->Attribute("type");

						IContent* content_prototype = NULL;
						_prototype_library->tryGetValue(type, content_prototype);
						if(content_prototype)
						{
							String key = child_element->Attribute("key");
							IContent* result = NULL;
							_content_table[content_prototype->getContentID()].tryGetValue(key, result);
							if(result)
							{
								String path = getCompleteContentString(child_element->Attribute("path"));
								if(result->_path == path)
								{
									// already loaded, get out of here
									return;
								}
								else
									error(StringBuilder() << "You are attempting to use the same key " << key << " twice");
							}
							else
							{
								result = (IContent*)content_prototype->instance();
								result->readXML(*child_element);
								result->_ref_count = 0;
								_content_table[content_prototype->getContentID()].add(key, result);
							}

						}
						else
							error(StringBuilder() << "Could not find Content type " << type);
					}
					
				}
			}
		}

		IContent* ContentLibrary::loadAndParseContentXML(const String& in_path, bool use_environment_variable)
		{
			String abso_path( in_path );

			if( use_environment_variable )
				abso_path = getCompleteContentString( in_path );			

 			FileInputStream fis( abso_path );
			//load the file and get its content into a String
			//FileInputStream fis(in_path);
			StringBuilder sb;

			for(int i = fis.read(); i >= 0; i = fis.read())
			{
				sb << (char)i;
			}
			fis.close();

			String& xml = (String)sb;

			//parse the String for what we need
			TiXmlDocument document;
			document.Parse(xml);
			
			TiXmlElement* root_element = document.RootElement();
			String root_tag(root_element->Value());

			if(root_tag.equals("Content"))
			{
				//first check and see if its already loaded by key
				String key = String(root_element->Attribute("key"));
				

				IContent* potentially_stored; 
				const char* search_string = root_element->Attribute("type");
				IContent* new_content_prototype;
				_prototype_library->tryGetValue( search_string, new_content_prototype );

				if(_content_table[new_content_prototype->getContentID()].tryGetValue(key, potentially_stored))
				{
					//this key is used check to make sure its the same path
					String temp_path(root_element->Attribute("path"));
					temp_path = getCompleteContentString( temp_path );

					if(potentially_stored->_path.equals(temp_path) ) 
					{
						//already loaded, we're done
						potentially_stored->_ref_count++;
						return potentially_stored; 
					}
					else
					{
						error("You are attempting to use the same key twice");
					}
				}
				//new piece of content, needs to be loaded
				
				IContent* new_content = (IContent*) new_content_prototype->instance();
				new_content->readXML(xml);

				new_content->_ref_count = 1;

				int id = new_content->getContentID();
				_content_table[id].add(key, new_content);

				//handle messages
				MSG msg = {0};
				if(PeekMessage( &msg, 0, 0, 0, PM_REMOVE ))
				{
					TranslateMessage( &msg );
					DispatchMessage( &msg );
				}

				return new_content;
			}
			return 0;
		}

		
	}
}